
package com.tools.cleanmaster.controller;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.SystemClock;

import com.tools.cleanmaster.CleanConstant;
import com.tools.cleanmaster.appinfo.AppInfoCompat;
import com.tools.cleanmaster.appinfo.AppInfoSnapshot;
import com.tools.cleanmaster.appinfo.AppManager;
import com.tools.cleanmaster.appinfo.AppManagerCompat;
import com.tools.cleanmaster.model.TrashType;
import com.tools.cleanmaster.model.db.AllTrashBean;
import com.tools.cleanmaster.model.db.CacheTrashBean;
import com.tools.cleanmaster.model.db.Decode;
import com.tools.cleanmaster.model.db.RegularTrashBean;
import com.tools.cleanmaster.model.db.ResidualTrashBean;
import com.tools.cleanmaster.model.db.TrashDB;
import com.tools.cleanmaster.model.item.AppTrashItem;
import com.tools.cleanmaster.model.item.RegularTrashData;
import com.tools.cleanmaster.model.item.RegularUtils;
import com.tools.cleanmaster.model.item.TrashItem;
import com.tools.cleanmaster.utils.FeatureConfig;
import com.tools.cleanmaster.utils.LogHelper;
import com.tools.cleanmaster.utils.TrashUtils;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 这个是应用缓存（非系统提供的）和 应用卸载残留的Scanner
 *
 * @author dianxin
 */
public class AppCacheAndResidualScanner extends TrashCacheAndResidualScanner {

    private AppManager mAppMgr = null;

    private Map<String, String> mDecodedPkgsMap;

    private HashMap<String, String> mDecodedPathsMap;

    private String[] mRootDirs;

    private String[] mWhiteList;

    private List<RegularTrashBean> mAllRegulars;

    private boolean mCacheResidualFinished = false;

    private boolean mRegularFinished = false;

    private AllTrashBean mAllTrashBean;

    private Object mScanFinishLocker = new Object();

    private long mCacheFileSize = 0;

    private long mStartTime;

    public AppCacheAndResidualScanner(Context context, TrashHandler handler) {
        super(context, handler);
        mAppMgr = AppManager.getInstance(context);
        mRootDirs = handler.getRootDirs();
    }

    @Override
    public void scan(int increaseProgress) {
        mIsScanning = true;
        // Debug.startMethodTracing("trash_cr", 300 * 1024 * 1024);
        doScan(increaseProgress);
        // Debug.stopMethodTracing();
        if (mCacheResidualFinished && mRegularFinished) {
            mIsScanning = false;
        }
    }

    private void doScan(int increaseProgress) {
        if (FeatureConfig.DEBUG_LOG) {
            mStartTime = System.currentTimeMillis();
            LogHelper.i(SUB_TAG, "AppCacheAndResidualScanner doScan increaseProgress="
                    + increaseProgress);
        }
        if (mStopRequested) {
            return;
        }
        mAllTrashBean = queryDbTrashMap();
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.i(SUB_TAG, "AppCacheAndResidualScanner doScan after queryDbTrashMap time="
                    + (System.currentTimeMillis() - mStartTime));
        }
        int progressDB = increaseProgress / 8;
        mTrashHandler.updateProgress(progressDB, null);
        if (mStopRequested) {
            return;
        }
        final int progressRegular = increaseProgress / 4;
        if (CleanConstant.CPU_CORES == 1) {
            scanRegularCache(progressRegular);
        } else {
            // 如果多核，则并行扫描
            new Thread(new Runnable() {
                @Override
                public void run() {
                    scanRegularCache(progressRegular);
                }
            }).start();
        }
        int progressCacheResidual = increaseProgress - progressDB - progressRegular;
        scanCacheResidual(progressCacheResidual);
    }

    private void scanCacheResidual(int progress) {
        long startCacheResidualTime;
        if (FeatureConfig.DEBUG_LOG) {
            startCacheResidualTime = System.currentTimeMillis();
        }
        if (mAllTrashBean == null) {
            mTrashHandler.updateProgress(progress, null);
            mTrashHandler.onTrashTypeFinish(TrashType.UNINSTALLED_APP, 0);
            mCacheResidualFinished = true;
            handleScanFinished();
            return;
        }
        // 通过 挂载点路径 + bean中携带的子路径 = 最终路径来生成对应的垃圾File
        long residualTotalSize = 0, cacheFileSize = 0;
        List<ResidualTrashBean> residualTrashBean = mAllTrashBean.residualTrashBean;
        List<CacheTrashBean> cacheTrashBean = mAllTrashBean.cacheTrashBean;

        // Debug.startMethodTracing("trash_cr", 300 * 1024 * 1024);
        // 卸载残留TrashItem的构建
        int progressCount = mRootDirs.length
                * ((residualTrashBean != null ? residualTrashBean.size() : 0) + (cacheTrashBean != null ? cacheTrashBean
                .size() : 0));
        if (progressCount == 0) {
            mTrashHandler.updateProgress(progress, null);
        }
        int progressIndex = 0;
        if (residualTrashBean != null) {
            for (String rootDir : mRootDirs) {
                for (ResidualTrashBean bean : residualTrashBean) {
                    File file = new File(rootDir, bean.filePath);
                    int itemProgress = calculateProgress(progress, progressIndex, progressCount);
                    progressIndex++;
                    if (file.exists() && !isInWhiteList(bean.filePath)) {
                        mTrashHandler.updateProgress(itemProgress, bean.softName);
                        TrashItem item = buildResidual(bean, file);
                        if (FeatureConfig.DEBUG_LOG) {
                            LogHelper.i(SUB_TAG, "residualTrashBean file=" + file);
                        }
                        if (!mStopRequested) {
                            mTrashesData.putTrash(TrashType.UNINSTALLED_APP, item);
                            residualTotalSize += item.size;
                            if (FeatureConfig.DEBUG_LOG) {
                                LogHelper.v(SUB_TAG, "[trash_residual] appName:" + item.appName
                                        + " filePath:" + item.filePath);
                            }
                        }
                    } else {
                        mTrashHandler.updateProgress(itemProgress, null);
                    }
                }
            }
        }

        // 应用缓存TrashItem的构建
        if (cacheTrashBean != null) {
            for (String rootDir : mRootDirs) {
                for (CacheTrashBean bean : cacheTrashBean) {
                    File file = new File(rootDir, bean.filePath);
                    int itemProgress = calculateProgress(progress, progressIndex, progressCount);
                    progressIndex++;
                    if (file.exists() && !isInWhiteList(bean.filePath)) {
                        AppInfoSnapshot info = mAppMgr
                                .getAppInfo(mDecodedPkgsMap.get(bean.pkgName));
                        mTrashHandler.updateProgress(itemProgress,
                                info != null ? info.getLabel() : null);
                        if (FeatureConfig.DEBUG_LOG) {
                            LogHelper.i(SUB_TAG, "cacheTrashBean file=" + file);
                        }
                        TrashItem item = buildCache(bean, file, info);
                        if (!mStopRequested && item != null) {
                            mTrashesData.putTrash(TrashType.APP_TRASH_FILE, item);
                            cacheFileSize += item.size;
                            if (FeatureConfig.DEBUG_LOG) {
                                LogHelper.i(SUB_TAG, "[trash_app_cache] appName:" + item.appName
                                        + " filePath:" + item.filePath);
                            }
                        }
                    } else {
                        mTrashHandler.updateProgress(itemProgress, null);
                    }
                }
            }
        }
        mTrashHandler.onTrashTypeFinish(TrashType.UNINSTALLED_APP, residualTotalSize);
        mCacheFileSize += cacheFileSize;
        mCacheResidualFinished = true;
        handleScanFinished();
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.i(SUB_TAG, "scanCacheResidual end time="
                    + (System.currentTimeMillis() - startCacheResidualTime));
        }
    }

    private long scanRegularCache(int progressRegular) {
        if (mStopRequested) {
            mRegularFinished = true;
            handleScanFinished();
            return 0l;
        }
        long startRegularTime = SystemClock.uptimeMillis();
        long size = 0;
        for (String rootDir : mRootDirs) {
            size += doRegularCacheTrashScan(rootDir);
        }
        mCacheFileSize += size;
        mTrashHandler.updateProgress(progressRegular, null);
        mRegularFinished = true;
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.i(SUB_TAG, "scanRegularCache: end time=" + (SystemClock.uptimeMillis() - startRegularTime));
        }
        handleScanFinished();
        return size;
    }

    /**
     * 处理正则的入口函数
     *
     * @param curRootDir 根目录
     *
     * @return 正则垃圾总大小
     */
    private long doRegularCacheTrashScan(String curRootDir) {
        long size = 0;
        HashMap<File, List<RegularTrashData>> allRegularTrashBean = new HashMap<File, List<RegularTrashData>>();
        File file = null;
        // 先将所有的正则表达式组织成一个路径(rootPath),对应多个正则表达式
        if (mAllRegulars != null) {
            for (RegularTrashBean rb : mAllRegulars) {
                if (mStopRequested) {
                    return size;
                }

                file = new File(curRootDir, rb.rootPath);
                if (file.exists()) {
                    List<RegularTrashData> list = allRegularTrashBean.get(file);
                    if (list == null) {
                        ArrayList<RegularTrashData> regularTrashDatas = new ArrayList<RegularTrashData>();
                        regularTrashDatas.add(new RegularTrashData(rb));
                        allRegularTrashBean.put(file, regularTrashDatas);
                    } else {
                        list.add(new RegularTrashData(rb));
                    }
                }
            }
        }
        // 对所有文件-正则表达式 开始遍历
        for (Map.Entry<File, List<RegularTrashData>> en : allRegularTrashBean.entrySet()) {
            if (mStopRequested) {
                return size;
            }
            File rootFile = en.getKey();
            final List<RegularTrashData> regularTrashs = en.getValue();
            RegularUtils.matchRegularList(rootFile, regularTrashs, 0);
            for (RegularTrashData data : regularTrashs) {
                if (mStopRequested) {
                    return size;
                }
                //计算 正则扫描出来的每个匹配路径的 文件数，大小。
                for (String pathItem : data.trashFilePaths) {
                    RegularUtils.getRegularTrash(new File(pathItem), data);
                }
                if (data.size != 0 && data.count != 0) {
                    size += buildRegularResult(rootFile, data);
                }
            }
        }
        return size;
    }

    /**
     * 构建 正则垃圾信息
     *
     * @param rootFile 正则固定路径的对象
     * @param data     正则对象
     *
     * @return size大小
     */
    private long buildRegularResult(File rootFile, RegularTrashData data) {
        String pkgName = mDecodedPkgsMap.get(data.regularTrashBean.pkgName);
        AppInfoCompat info = null;
        try {
            info = AppManagerCompat.getAppInfo(pkgName);
        } catch (PackageManager.NameNotFoundException e) {
        }
        String appName = info == null ? "" : info.getLabel();
        AppTrashItem item = new AppTrashItem();
        item.appName = appName;
        item.trashType = TrashType.APP_TRASH_FILE;
        item.filePath = rootFile.getAbsolutePath();
        item.pkgName = pkgName;
        item.id = data.regularTrashBean.id;
        item.cleanSuggest = data.regularTrashBean.isDeleteDir;
        item.isSelectedDefault = item.isSelected = item.cleanSuggest == AppTrashItem.CLEAN_SUGGEST_CLEAN;
        item.alertInfo = data.regularTrashBean.alertInfo;
        item.contentType = data.regularTrashBean.contentType;
        item.itemName = data.regularTrashBean.itemName;
        item.isDir = true;
        item.size = data.size;
        item.fileCount = (int) data.count;
        item.isRegular = true;
        item.regularPaths = data.trashFilePaths;
        if (!mStopRequested) {
            mTrashesData.putTrash(TrashType.APP_TRASH_FILE, item);
            if (FeatureConfig.DEBUG_LOG) {
                LogHelper.i(SUB_TAG, "regular_cache result"
                        + " appName:" + item.appName
                        + " deleteLev:" + item.cleanSuggest
                        + " filePath:" + item.filePath
                        + " sign=" + TrashUtils.isUseJniFunction());
                for (String strItem : data.trashFilePaths) {
                    LogHelper.v(SUB_TAG, "regular_cache result item:" + strItem);
                }
            }
        }
        return item.size;
    }

    private void handleScanFinished() {
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.i(SUB_TAG,
                    "AppCacheAndResidualScanner handleScanFinished mCacheResidualFinished="
                            + mCacheResidualFinished + ",mRegularFinished=" + mRegularFinished);
        }
        synchronized(mScanFinishLocker) {
            if (mIsScanning && mCacheResidualFinished && mRegularFinished) {
                mTrashHandler.onTrashTypeFinish(TrashType.APP_TRASH_FILE, mCacheFileSize);
                if (FeatureConfig.DEBUG_LOG) {
                    LogHelper.i(
                            SUB_TAG,
                            "AppCacheAndResidualScanner doScan end time="
                                    + (System.currentTimeMillis() - mStartTime));
                }
                mIsScanning = false;
            }
        }
    }

    private boolean isInWhiteList(String filePath) {
        if (mWhiteList == null || filePath == null) {
            return false;
        }
        for (String whitePath : mWhiteList) {
            if (filePath.startsWith(whitePath)) {
                return true;
            }
        }
        return false;
    }

    private AllTrashBean queryDbTrashMap() {
        List<String> installedAppsPkgNames = new ArrayList<String>();
        PackageManager pm = mContext.getPackageManager();
        List<ApplicationInfo> installedApps = pm.getInstalledApplications(0);
        for (ApplicationInfo appInfo : installedApps) {
            installedAppsPkgNames.add(appInfo.packageName);
        }
        List<String> dirs = new ArrayList<String>();
        for (String dir : mRootDirs) {
            File file = new File(dir);
            String[] files = file.list();
            if (files == null) {
                continue;
            }
            for (String fileName : files) {
                if (new File(file, fileName).isDirectory() && !dirs.contains(fileName)) {
                    dirs.add(fileName);
                }
            }
        }
        if (dirs.isEmpty()) {
            return null;
        }
        // 生成数据
        mDecodedPathsMap = new HashMap<String, String>(dirs.size());
        for (int i = 0, len = dirs.size(); i < len; i++) {
            String filePath = dirs.get(i);
            dirs.set(i, Decode.encode(filePath));
            mDecodedPathsMap.put(dirs.get(i), filePath);
        }
        // 生成加密数据和对应关系 install app pkgname
        mDecodedPkgsMap = new HashMap<String, String>(installedAppsPkgNames.size());
        for (int i = 0, len = installedAppsPkgNames.size(); i < len; i++) {
            String pkgName = installedAppsPkgNames.get(i);
            installedAppsPkgNames.set(i, Decode.encode(pkgName.toLowerCase()));
            mDecodedPkgsMap.put(installedAppsPkgNames.get(i), pkgName);
        }
        long dbTimeStart;
        if (FeatureConfig.DEBUG_LOG) {
            dbTimeStart = System.currentTimeMillis();
        }
        TrashDB trashDB = TrashDB.getInstance();
        if (FeatureConfig.DEBUG_LOG) {
            LogHelper.i(SUB_TAG, "queryDbTrashMap init TrashDB time="
                    + (System.currentTimeMillis() - dbTimeStart));
        }
        AllTrashBean result = trashDB.getAllTrash(dirs, installedAppsPkgNames,TrashDB.QUICK);
        mAllRegulars = trashDB.getAllRegulars(installedAppsPkgNames,TrashDB.QUICK);
        if (mWhiteList == null) {
            mWhiteList = trashDB.getWhiteList();
        }
        return result;
    }

    private AppTrashItem buildResidual(ResidualTrashBean bean, File file) {
        AppTrashItem item = new AppTrashItem();
        item.pkgName = bean.pkgNames;// mDecodedPkgsMap.get(bean.pkgNames);
        item.trashType = TrashType.UNINSTALLED_APP;
        item.filePath = file.getAbsolutePath();
        item.cleanSuggest = bean.deleteLevel;
        item.isSelectedDefault = item.isSelected = item.cleanSuggest == AppTrashItem.CLEAN_SUGGEST_CLEAN;
        item.pathDesp = "";
        item.alertInfo = bean.alertInfo;
        item.isDir = file.isDirectory();
        item.appName = bean.softName;

        long[] countAndSize = TrashUtils.getFileCountAndSizeWithDepth(file, TrashUtils.DEPTH_MAX);
        item.fileCount = (int) countAndSize[0];
        item.size = countAndSize[1];

        return item;
    }

    private AppTrashItem buildCache(CacheTrashBean bean, File file, AppInfoSnapshot info) {
        if (info == null) {
            if (FeatureConfig.DEBUG_LOG) {
                LogHelper
                        .i(SUB_TAG,
                                "buildCache info == null file=" + file
                                        + ",mDecodedPkgsMap.get(bean.pkgName)="
                                        + mDecodedPkgsMap.get(bean.pkgName));
            }
            return null;
        }
        AppTrashItem item = new AppTrashItem();
        item.appName = info == null ? "" : info.getLabel();
        item.trashType = TrashType.APP_TRASH_FILE;
        item.filePath = file.getAbsolutePath();
        item.pkgName = mDecodedPkgsMap.get(bean.pkgName);
        item.cleanSuggest = bean.deleteLevel;
        item.isSelectedDefault = item.isSelected = item.cleanSuggest == AppTrashItem.CLEAN_SUGGEST_CLEAN;
        item.pathDesp = bean.description;
        item.alertInfo = bean.alertInfo;
        item.contentType = bean.contentType;
        item.itemName = bean.itemName;
        item.isDir = file.isDirectory();

        long[] countAndSize = TrashUtils.getFileCountAndSizeWithDepth(file, TrashUtils.DEPTH_MAX);
        item.fileCount = (int) countAndSize[0];
        item.size = countAndSize[1];

        return item;
    }

}
